import random
import time

import es
import gamethread
import playerlib
import vecmath

import wcs

raceName = "Shadow Hunter"
race     = wcs.races[raceName]

""" Load the strings into the variable text """
text     = race.loadStrings()

""" ############################### """
""" ### Begin the configuration ### """
""" ############################### """

config   = race.config()

raceMinLevel = config.cvar("raceMinLevel", 60, "The minimum total level a user must have before having the ability to play Shadow Hunter")


config.text("")
config.text("********************")
config.text("Healing Wave")
config.text("  This skill is used by binding a key to +ability2.")
config.text("  when the player looks at another team mate, a healing")
config.text("  beam will bounce from player to player, the beam slowly")
config.text("  getting weaker.")
config.text("********************")
config.text("")

healingMaxlevel        = config.cvar("healingMaxLevel",         5, "The maximum level of the Healing Wave skill")
healingLevelInterval   = config.cvar("healingLevelInterval",    2, "The intervals between the abilities to upgrade their Healing Wave skill")
healingStartMultiplier = config.cvar("healingStartMultiplier", 15, "How much health the first person will receive multipied by the players level.")
healingLossPercent     = config.cvar("healingLossPercent",     40, "How much effect each jump will loose. ")

config.text("")
config.text("********************")
config.text("Hex")
config.text("  This skill has a chance of halving a players speed for X amount of seconds.")
config.text("********************")
config.text("")
hexMaxLevel      = config.cvar("hexMaxLevel",      6, "The maximum level of the Hex skill")
hexLevelInterval = config.cvar("hexLevelInterval", 1, "The intervals between the abilities to upgrade their Hex skill")
hexPercentage    = config.cvar("hexPercentage",    18, "This is the chance the skill will happen multiplied by their level.")

config.text("")
config.text("********************")
config.text("Serpent Wards")
config.text("  This skill places a serpent ward at the players current location which attackes")
config.text("  nearby enemies. Used by pressing the ability +ability")
config.text("********************")
config.text("")
serpentMaxLevel      = config.cvar("serpentMaxLevel",      6, "The maximum level of the Serpent Wards skill")
serpentLevelInterval = config.cvar("serpentLevelInterval", 3, "The intervals between the abilities to upgrade their Serpent Wards skill")
serpentMaxWards      = config.cvar("serpentMaxWards",      2, "The maximum amount of wards at any 1 time.")

config.text("")
config.text("********************")
config.text("Big Bad Voodo")
config.text("  This ultimate turns all near team-mates invinisible for X seconds.")
config.text("********************")
config.text("")
voodoMinLevel      = config.cvar("voodoMinLevel",      6, "The required level to be able to buy level 1 of Big Bad Boodo")
voodoMaxLevel      = config.cvar("voodoMaxLevel",      6, "The maximum level of the Big Bad Boodo skill")
voodoLevelInterval = config.cvar("voodoLevelInterval", 3, "The intervals between the abilities to upgrade their Big Bad Boodo skill")
voodoTime          = config.cvar("voodoTime",          1, "The amount of seconds team-mates will stay invinsible for multiplied by the players voodo level.")

config.load(True)

""" ##################### """
""" ### END OF CONFIG ### """
""" ##################### """

race.registerSkill("Healing Wave", int(healingMaxlevel), int(healingLevelInterval), "Calls forth a wave of energy that heals a target and bounces to nearby friendlies. (+ability2)")
race.registerSkill("Hex", int(hexMaxLevel), int(hexLevelInterval), "Have a chance to slow down your enemies!")
race.registerSkill("Serpent Ward", int(serpentMaxLevel), int(serpentLevelInterval), "Summons an immobile serpentine ward to attack the Shadow Hunters enemies (+ability)")
race.registerUltimate("Big Bad Voodo", int(voodoMinLevel), int(voodoMaxLevel), int(voodoLevelInterval), "Turns all friendly units invulnerable in an area around the Shadow Hunter.")

race.registerMinLevel(int(raceMinLevel))
                
class SerpentWardManager(object):
    """
    This object acts as a container to contain the objects of the SerpentWard
    instances. This object simulates a dict with a custom API to allow normal
    dictionary functions to extend to the specifities of this object API.
    """
    def __init__(self):
        """
        Default constructor, ensure the objects are created
        """
        self.players = {}
        
    def __getitem__(self, userid):
        """
        self[<key>] <==> self.__getitem__(<key>)
        
        Assume we want to return the sepent ward manager represented by the
        userid.
        
        @param int userid The ID of the user to retrieve
        @return dict|None The dictionary containing the serpent wards
        """
        userid = int(userid)
        return self.players.get(userid, None)
    
    def __delitem__(self, userid):
        """
        del self[<key>] <==> self.__delitem__(<key>)
        
        Assume we want to remove a player instance, as so, remove it
        """
        self.removePlayer(userid)
        
    def clear(self):
        """
        Clears all players from the object, essentially this just destroyes
        all serpent wards currently active.
        """
        self.players.clear()
        
    def addPlayer(self, userid):
        """
        Adds a player into the serpent ward manager. Creates a new userid space
        within the dictionary object.
        
        @param int userid The ID of the user this object represents
        """
        userid = int(userid)
        self.players[userid] = list()
        
    def addWard(self, userid, x, y, z):
        """
        Adds a ward at a particular place to a user's object storage.
        
        @param int userid The ID of the user
        @param float x The x coordinate of the ward's origin
        @param float y The y coordinate of the ward's origin
        @param float z The z coordinate of the ward's origin
        """
        userid = int(userid)
        if userid not in self.players:
            self.addPlayer(userid)
        self.players[userid].append(SerpentWard(userid, x, y, z))
        
    def removeWard(self, userid, wardInstance):
        """
        Renoves a ward from a player by a given instance. This will only remove
        if the instance can be found in the player's dict.
        
        @param int userid The ID of the user to remove the ward instance
        @param SerpentWard The instance of which to remove from the player's object
        """
        userid = int(userid)
        if userid not in self.players:
            return
        if wardInstance in self.players[userid]:
            self.players[userid].remove(wardInstance)
        
    def clearPlayerWards(self, userid):
        """
        Removes all a ward who are owned by a particular user
        
        @param int userid The ID of a user
        """
        userid = int(userid)
        if userid not in self.players:
            return
        del self.players[userid][:]
        
    def removePlayer(self, userid):
        """
        Removes a player from being stored in the dictionary; this doesn't just
        remove the wards, this removes the player's instance as well.
        
        @param int userid The ID of the users
        """
        userid = int(userid)
        if userid in self.players:
            del self.players[userid]
                
class SerpentWard(object):
    """
    This object should be created for each serpent ward placed on the map.
    This essentially is here to create a loop to check for all nearby enemies
    and damage them depending on the wards level. After the timer has ran out,
    destroy itself.
    """
    def __init__(self, userid, x, y, z):
        """
        Default constructor, executed when this object is created. Store all
        values necessary with this object so we only create the values once.
        
        @param int userid The ID of the user this ward belongs to
        @param float x The x coordinate of the ward's origin
        @param float y The y coordinate of the ward's origin
        @param float z The z coordinate of the ward's origin
        """
        self.userid = int(userid)
        player = wcs.players[userid]
        self.level = player[raceName]["Serpent Ward"]
        self.x = x
        self.y = y
        self.z = z
        self.delayName = "serpent_loop_%s" % self.userid
        self.location = vecmath.Vector(x, y, z)
        self.start = time.time()
        self.loop()
        
    def __del__(self):
        """
        Default destructor, executed when this object is destoryed. Remove any
        unecessary outstanding delays.
        """
        gamethread.cancelDelayed(self.delayName)
        
    def loop(self):
        """
        Executed each iteration of the loop. This is where we shall check all
        enemy locations, and damage players within the range of the ward.
        """
        if time.time() - self.start <= 20:
            #wcs.effect.RingCustom("#a", "0", "sprites/lgtning.vmt", x, y, z, 20, self.level * 40, 1, 20, 100, 1, 255, 150, 70, 100, 10)
            players = es.getUseridList()
            teamToCheck = 5 - es.getplayerteam(self.userid)
            players = filter(lambda x: es.getplayerteam(x) == teamToCheck, players)
            players = filter(lambda x: not es.getplayerprop(x, "CBasePlayer.pl.deadflag"), players)
            for player in players:
                target = vecmath.Vector(es.getplayerlocation(player))
                if (target - self.location).length() <= self.level * 40:
                    wcs.players[player].damage(self.level * random.randint(4, 6), 32, self.userid)
                    #wcs.effect.BeamCustom("#a", "0", "sprites/lgtning.vmt", x, y, z + 30, xx, yy, zz + 30, 1, 10, 20, 255, 150, 70, 255)
            gamethread.delayedname(1, self.delayName, self.loop)
        else:
            es.tell(self.userid, "#multi", text("serpent gone", lang=playerlib.getPlayer(self.userid).get("lang")))
            wards.removeWard(self.userid, self)
        
wards = SerpentWardManager()
                
def player_ability_on(event_var):
    """
    Executed when a player presses their ability button. If possible, create a
    ward and drop it at the player's current location.
    """
    userid  = event_var["userid"]
    player = wcs.players[userid]
    serpent = player[raceName]["Serpent Ward"]
    if serpent:
        cooldown = player.getCoolDown("Serpent Ward")
        if not cooldown:
            x, y, z = es.getplayerlocation(userid)
            if len(wards[userid]) < int(serpentMaxWards):
                x, y, z = es.getplayerlocation(userid)
                wards.addWard(userid, x, y, z)
                player.setCoolDown("Serpent Ward", 10)
            else:
                es.tell(userid, "#green", text("max wards", lang = playerlib.getPlayer(userid).get("lang")))
        else:
            es.tell(userid, "#multi", text("cooldown", {"skill":"Serpent Ward", "time": cooldown}, playerlib.getPlayer(userid).get("lang")))
            
def round_end(event_var):
    """
    Executed when the round ends, ensure we remove any current wards.
    """
    wards.clear()
    
def player_disconnect(event_var):
    """
    Executed when the player disconnects, remove all the player's wards/
    """
    userid = event_var["userid"]
    wards.clearPlayerWards(userid)
    
def player_ability_on2(event_var):
    """
    Executred when a player pressess their +ability2 key, start a healing wave.
    Get their current view player, then start the bounce from that player.
    """
    userid = event_var["userid"]
    player = wcs.players[userid]
    level = player[raceName]["Healing Wave"]
    if level:
        cooldown = player.getCoolDown("Healing Wave")
        if not cooldown:
            heal = level * int(healingStartMultiplier)
            #x, y, z = es.getplayerlocation(userid)
            #wcs.effect.BeamCustom("#a", 0, "sprites/halo01.vmt", x, y, z, x, y, z + 400, 5, 80, 40, 255, 0, 255, 155)
            player.setCoolDown("Healing Wave", 20)
            viewPlayer = player.getViewPlayer()
            if viewPlayer:
                healBounce(viewPlayer, heal, [userid])
            else:
                es.tell(userid, "#multi", text("healing no player", lang = playerlib.getPlayer(userid).get("lang")))
    else:
        es.tell(userid, "#multi", text("cooldown", {"skill":"Healing Wave", "time": cooldown}, playerlib.getPlayer(userid).get("lang")))
        
def healBounce(target, heal, banned):
    """
    Executed when we want to bounce the heal to another random player in range
    of the last player.
    
    @param int target The ID of the target to bounce to
    @param int heal The amount of health to heal
    @param list banned A list of userids who have already been healed
    """
    if target and es.exists("userid", target):
        player = wcs.players[target]
        player.heal(heal)
        banned.append(target)
        team    = es.getplayerteam(target)
        playerList = es.getUseridList()
        playerList = filter(lambda x: x not in banned, playerList)
        playerList = filter(lambda x: not es.getplayerprop(x, "CBasePlayer.pl.deadflag"), playerList)
        playerList = filter(lambda x: es.getplayerteam(x) == team, playerList)
        playerList = filter(lambda x: player.distance(x) <= 500, playerList)
        if playerList:
            randomPlayer = random.choice(allowed)
            #wcs.effect.Beam4(target, randomPlayer, 4, 255, 0, 0)
            #wcs.effect.Ring4(randomPlayer, 150, 75, 4, 60, 80, 0, 255, 0)
            heal = int(round(heal * ((100 - (int(healingLossPercent))) / 100.)))
            wcs.players[randomPlayer].heal(heal)
            gamethread.delayed(0.5, healBounce, (randomPlayer, heal, banned ))
        
def player_hurt(event_var):
    """
    Executed when a player is hurt, see if the attacker has a level in hex, if
    so, then give a random chance of hexing the victim.
    """
    userid   = event_var["userid"]
    attacker = event_var["attacker"]
    if event_var["es_userteam"] != event_var["es_attackerteam"] and attacker and not wcs.players[userid]["Hexed"]:
        hex = wcs.players[attacker][raceName]["Hex"]
        if hex and random.randint(1, 100) <= (int(hexPercentage) * hex):   
            player = wcs.players[userid]
            player["hexed"] = True
            speed   = player["maxSpeed"]
            divider = speed / (1 + (hex * 0.2))
            # This should divide the speed by: 1.2, 1.4, 1.6, 1.8, 2.0, 2.2
            player.speed(speed / divider)
            tokens             = {}
            tokens["speed"]    = round(100 * divider  * speed, 1)
            tokens["attacker"] = event_var["es_attackername"]
            tokens["victim"]   = event_var["es_username"]
            es.tell(userid,   "#multi", text("hex victim",   tokens, playerlib.getPlayer(userid).get("lang")))
            es.tell(attacker, "#multi", text("hex attacker", tokens, playerlib.getPlayer(attacker).get("lang")))
            gamethread.delayed(3, player.speed, speed)
            gamethread.delayed(3, player.__delitem__, "hexed")
        
def player_ultimate_on(event_var):
    """
    Executed when the player presses their ultiamte key. Ensure that all players
    within a given range are made invinisble for a certain amount of time.
    """
    userid = event_var["userid"]
    player = wcs.players[userid]
    voodo = player[raceName]["Big Bad Voodo"]
    if voodo:
        player.near(voodo * 50, god, "%s" % ("#t" if event_var["es_userteam"] == "2" else "#ct"))
        player.setCoolDown("Big Bad Voodo", 30)
        x, y, z = es.getplayerlocation(userid)
        #wcs.effect.RingCustom("#a", "0", "sprites/lgtning.vmt", x, y, z, 20, voodo * 50, 1, 20, 100, 1, 255, 0, 0, 100, 10)

def god(userid, target):
    """
    Executed for every user in range of the player when they hit their ultimate
    key. Turn them invinsible for a certain amount of time dependant on the
    user's level.
    """
    voodo = wcs.players[target][raceName]["Big Bad Voodo"]
    wcs.players[userid].god(voodo * int(voodoTime))
    #wcs.effect.Ring4(userid, 150, 50, voodo, 50, 100, 255, 0, 255)
    es.tell(userid, "#multi", text("voodo", {"time": voodo * int(voodoTime)}, playerlib.getPlayer(userid).get("lang")))
